package core.sim.ring;

import graphics.*;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;

import stats.*;
import core.*;
import core.genetic.*;
import core.neural.*;
import core.sim.*;

public class RingWorldAdvSim implements SingleSimulation {

	@Override
	public int getInfluencesCount() {
		return 1;
	}

	@Override
	public int getInputCounts() {
		return 5+1+ Mode.mode.FEROMONS+ Mode.mode.MEMORY;
	}

	@Override
	public int getOutputCounts() {
		return 1+ Mode.mode.FEROMONS+ Mode.mode.MEMORY;
	}

	@Override
	public int getSurroungingsCount() {
		return 5;
	}

	
	
	public static final int highestState = 5;
	public static final int range = 6;
	public static final double rocketRate = 0.2/10;
	public static final double birdRate = 0.15/10;
	public static final int turns = 40;
	
	
	// indexes in object arrays (rockets and birds)
	private static final int POS = 0;
	private static final int TTL = 1;
	private static final int DIR = 2;
	
	// indexes in surrountings array
	private static final int RDP = 0;
	private static final int RDN = 1;
	private static final int BDP = 2;
	private static final int BDN = 3;
	private static final int STATE = 4;
	
	
	
	
	public RingWorldAdvSim() {
		
	}
	
	
	private double[][] feromons;
	private double[][] memory;
	private double[][] surroundings;
	private double[][] outputs;
	private double[][] inputs; 
	
	private double[][] selfFeromons;
	
	private LinkedList<int[]> rockets;
	private LinkedList<int[]> birds;
	
	private int[] state;
	private double[] helt;
	
	private LogWindow log;
	
	
	@Override
	public synchronized double[] playSimulation(Individual[] individuals) {

		
		int n = individuals.length;
		init(n);
		
		for (int i = 0; i <= turns; i++)
			execTurn(individuals, n);
		
		
		double[] res = new double[n];	

		double sumaHelta = 0;
		for(int i = 0;i<n;i++)
			sumaHelta+=helt[i];
		
		sumaHelta /=n;
		
		for(int i = 0;i<n;i++)
			res[i] = -helt[i];//sumaHelta;
		
		Statistics.addTeamResult(sumaHelta);
		
		
//		for(int i = 0;i<n;i++)
//			Statistics.addResult(helt[i]);		

		
//		System.out.println(Arrays.toString(state));
		return res;
	}

	private Random random;
	
	private void init(int n) {
		feromons = new double[n][ Mode.mode.brain.FEROMONS];
		memory = new double[n][ Mode.mode.brain.MEMORY];
		surroundings = new double[n][5];

		outputs = new double[n][];
		inputs = new double[n][];
		
		selfFeromons = new double[n][Mode.mode.brain.FEROMONS];

		
		// int[] {position, ttl, direction}
		rockets = new LinkedList<int[]>();
		birds = new LinkedList<int[]>();	
		
		state = new int[n];
		helt = new double[n];
		for(int i = 0;i<n;i++)
			helt[i] = 1;
		random = new Random(15);
		
		
	}
	
	private void execTurn(Individual[] individuals, int n) {
		for(int j = 0;j<n;j++) {
			for(int k = 0;k<4;k++)
				surroundings[j][k] = 0;
			surroundings[j][STATE] = state[j];
		}
		
		
		while (rockets.size()>0 && rockets.getFirst()[TTL]<=1) 
			rockets.removeFirst();
		while (birds.size()>0 && birds.getFirst()[TTL]<=1) 
			birds.removeFirst();
		
		if (random.nextDouble() < rocketRate*n) 
			rockets.addLast(new int[]{random.nextInt(n), range, random.nextInt(2)>0?1:-1});
		
		if (random.nextDouble() < birdRate*n) 
			birds.addLast(new int[]{random.nextInt(n), range, random.nextInt(2)>0?1:-1});
		
					
		for(int[] rocket : rockets) 
		{										
			rocket[TTL]--;				
			double str = 1.0;//*(range-rocket[TTL])/range;
			double change = (0.01*state[rocket[POS]]*state[rocket[POS]]*str);
			if (isMovie)
				log.updateUdarilaRaketa(rocket[POS], rocket[TTL], helt[rocket[POS]], change);
			helt[rocket[POS]] -= change;
			rocket[POS] = (rocket[POS]+rocket[DIR]+n)%n;
			if (rocket[DIR]>0) 
				surroundings[rocket[POS]][RDP]+= str;
			else surroundings[rocket[POS]][RDN]+= str;
		}

		for(int[] bird : birds) 
		{												
			bird[TTL]--;				
			double str = 1.0;//*(range-bird[TTL])/range;
			double change = (0.01*(state[bird[POS]]-1)*state[bird[POS]]*str);
			if (isMovie)
				log.updateGricnuoPticu(bird[POS], bird[TTL], helt[bird[POS]], change);
			helt[bird[POS]] += change;
			bird[POS] = (bird[POS]+bird[DIR]+n)%n;
			if (bird[DIR]>0) 
				surroundings[bird[POS]][BDP]+= str;
			else surroundings[bird[POS]][BDN]+= str;
		}
							
		for (int j = 0; j < n; j++)
		{
			inputs[j] = BrainParsing.mergeInputs(surroundings[j],	feromons[j], memory[j]);
			outputs[j] = Mode.mode.brain.calculateOutputs(individuals[j], inputs[j] );
		}
		
		for (int j = 0; j < n; j++)
			for (int k = 0; k <  Mode.mode.brain.FEROMONS; k++)
				feromons[j][k] = 0;

		for (int j = 0; j < n; j++) {
			double[][] splitted = BrainParsing.splitOutput(outputs[j]);
			double d = splitted[BrainParsing.INFLUENCE_POS][0];
			selfFeromons[j] = splitted[BrainParsing.FEROMONS_POS];
			if (d>0.7 && state[j]<highestState) {
				state[j]++;
				if (isMovie)
					log.updateDizheState(j, state[j]);
			}
			if (d<0.3 && state[j]>0) {
				state[j]--;
				if (isMovie)
					log.updateSpustaState(j, state[j]);
			}
//			System.out.printf("%.3f ",d);
			
			System.arraycopy(splitted[BrainParsing.MEMORY_POS], 0, memory[j], 0,  Mode.mode.brain.MEMORY);
			
			for (int k = -2; k < 0; k++)
				for (int t = 0; t <  Mode.mode.brain.FEROMONS/2; t++)						
					feromons[(j+k+n)%n][t] += splitted[BrainParsing.FEROMONS_POS][t];

			for (int k = 1; k <= 2; k++)
				if (0 != k) {
					for (int t = Mode.mode.brain.FEROMONS/2; t<Mode.mode.brain.FEROMONS; t++)						
						feromons[(j+k+n)%n][t] += splitted[BrainParsing.FEROMONS_POS][t];
				}
		}
//		System.out.println();
		
	}
	
	
	
	@Override
	public void drawSimulation(Population pop, double[] values) {
//		if (( Mode.mode.currentGeneration&16)==0) {

			Individual[] individuals = new Individual[ Mode.mode.teamNb];			
			int n = individuals.length;					
						
//			double[] cumS = Utils.calcCumulativeProb(values, values.length);
//			for(int i = 0;i<n;i++)
//				individuals[i] = pop.get(Utils.randomW(cumS));

			int max = 0;
			for (int j = 1; j < values.length; j++)
				if (values[max] < values[j])
					max = j;
			for(int i = 0;i<n;i++)
				individuals[i] = new Individual(pop.get(max));

//			double[] checksum1 = playSimulation(individuals);
//			double[] checksum2 = playSimulation(individuals);
//			if (!Arrays.equals(checksum1, checksum2)) {
//				System.out.println("----" +Arrays.toString(checksum1));
//				System.out.println("----" +Arrays.toString(checksum2));
//			}
			
			
			int[] counts = new int[n];
			init(n);
			
			for (int i = 0; i <= turns; i++) {
				execTurn(individuals, n);
				for(int j = 0;j<n;j++)
					counts[state[j]]++;
			}
			
			synchronized (GraficPainter.drawMonitor) {
				MainApp.freal.clear();
				MainApp.findiv.clear();

				for(int j = 0;j<=highestState;j++) {
					MainApp.findiv.add(-(double)counts[j]);
					MainApp.freal.add(0.0);
				}
				MainApp.freal.add(-1.0*turns*n);
				
//				MainApp.end = true;
			}
//		}						
		
	}
	
	
	private int counter;
	private boolean isMovie = false;
	
	public void executeForMovie(Individual[] individuals) {
		int n = individuals.length;
		init(n);
		MainApp.getApp().endForMovie();
		isMovie = true;
		log = new LogWindow("Ringworld sim log");
		log.setSize(400, 600);
		//ovde inicijalizujes podatke za iscrtavanje
		
		for (counter = 0; counter <= turns; counter++) {
			synchronized (this) {
				execTurn(individuals, n);
			}
			// ovde spremas podatke za iscrtavanje, i vrtis krugove
			log.updateNewTurn(counter);
			MainApp.getApp().repaint();
			try { Thread.sleep(1000); }
			catch (InterruptedException e) { e.printStackTrace(); }
		}		
	}
	
	int ccc = 0;
	
	public synchronized void drawMovie(Graphics2D g2, Point2D dim) {
		
		//natji sklad izmedju dimenzije prozora i broja individua
		int size = state.length;
		double bigRadius = (Math.PI * (dim.getX()) / (size+Math.PI)/2.1);
		double smallRadius = 2*bigRadius / 3; 
		
//		double bigRadius = 25;
//		double smallRadius = 15;
		
//		g2.setColor(Color.GRAY);
//		g2.fillOval(0,0, (int)dim.getX(), (int)dim.getY());
//		g2.setColor(Color.WHITE);
//		g2.fill(new Ellipse2D.Double(bigRadius*2, bigRadius*2,	dim.getX()-bigRadius*4, dim.getY()-bigRadius*4));
//		g2.setColor(Color.BLACK);
		
		//priprema sveta i crtanje prstena
		g2.setStroke(new BasicStroke(0.3f));
		g2.setColor(Color.BLACK);
		g2.draw(new Ellipse2D.Double(0, 0, dim.getX(), dim.getY()));
		g2.draw(new Ellipse2D.Double(bigRadius*2, bigRadius*2,	dim.getX()-bigRadius*4, dim.getY()-bigRadius*4));
		
		//sad treba crtati individue;
		
		for(int i = 0; i<size; i++)
		{
			Point2D centar = getPoint(i*2*Math.PI/size, (dim.getX()/2-bigRadius), (dim.getY()/2-bigRadius), dim);
			Point2D start = new Point2D.Double(centar.getX()-bigRadius, centar.getY()-bigRadius);

			g2.setStroke(new BasicStroke(1f));

			
			//sad crtaj feromone
			// uradi za vishe od 3 feromona, da deli 2PI na nFeromon delova
			int b = 0;
			int nFeromon = Mode.mode.FEROMONS;
			for(double fi = 0; fi<2*Math.PI-0.01;fi+=2*Math.PI/nFeromon)
			{			
				//podesi boju
				int jacina = (int)(128 + selfFeromons[i][b]*127);
				g2.setColor(new Color(b%3==0?jacina:0,b%3==1?jacina:0,b%3==2?jacina:0));
				g2.fill(new Arc2D.Double(start.getX(), start.getY(), bigRadius*2, bigRadius*2, fi*360/2/Math.PI, 360/nFeromon, Arc2D.PIE));
				g2.setColor(Color.BLACK);
				g2.drawLine((int)(centar.getX()+10*Math.cos(fi)),(int)(centar.getY()+10*Math.sin(fi)),(int)(centar.getX()+bigRadius*Math.cos(fi)),(int)(centar.getY()+bigRadius*Math.sin(fi)) );
				b++; //uvek menjamo samo jednu od 3 boje
			}
			
			int boja = 55+200/highestState*state[i];
			g2.setColor(new Color(boja,boja,boja));					
			g2.fill(new Ellipse2D.Double(centar.getX()-smallRadius, centar.getY()-smallRadius, 2*smallRadius, 2*smallRadius));
			g2.setColor(new Color(255, 150, 0));
			g2.setFont(new Font("Arial", Font.BOLD, 12));
			g2.drawString(((helt[i]<1)?" ":"") +(int)(100*helt[i]), (int)centar.getX()-10, (int)centar.getY()+4);

			g2.setColor(Color.BLACK);
			g2.draw(new Ellipse2D.Double(start.getX(),start.getY() , bigRadius*2, bigRadius*2));
			g2.draw(new Ellipse2D.Double(centar.getX()-smallRadius, centar.getY()-smallRadius, smallRadius*2, smallRadius*2));			

		}
		
		//zavrshene jedinke, daj raketle i ptice
		g2.setStroke(new BasicStroke(3f));
		g2.setColor(Color.GREEN);
		for(int[] bird : birds)
		{
			double fi = (bird[POS]-0.5* bird[DIR])*2*Math.PI/size;
			double fiD = (bird[POS]-0.3*bird[DIR])*2*Math.PI/size;
			Point2D start = getPoint(fi, dim.getX()/2-2*bigRadius - 20, dim.getY()/2-2*bigRadius - 20, dim);
			Point2D dest = getPoint(fiD, dim.getX()/2-2*bigRadius - 20, dim.getY()/2-2*bigRadius - 20, dim);
			//
			Point2D bliza = getPoint(fi, dim.getX()/2-2*bigRadius - 32, dim.getY()/2-2*bigRadius - 32, dim);
			Point2D dalja = getPoint(fi, dim.getX()/2-2*bigRadius - 8, dim.getY()/2-2*bigRadius - 8, dim);

			Point2D blizaD = getPoint(fiD, dim.getX()/2-2*bigRadius - 32, dim.getY()/2-2*bigRadius - 32, dim);
			Point2D daljaD = getPoint(fiD, dim.getX()/2-2*bigRadius - 8, dim.getY()/2-2*bigRadius - 8, dim);
			
			
			Path2D tica = new Path2D.Double();
			tica.moveTo(start.getX(),start.getY());
			tica.curveTo(dest.getX(), dest.getY(), blizaD.getX(), blizaD.getY(), bliza.getX(),bliza.getY());
			tica.moveTo(start.getX(),start.getY());
			tica.curveTo(dest.getX(), dest.getY(), daljaD.getX(), daljaD.getY(), dalja.getX(),dalja.getY());			
			g2.draw(tica);	
		}
		
		g2.setColor(Color.RED);
		for(int[] rocket : rockets)
		{
			double fi = (rocket[POS]-0.5*rocket[DIR])*2*Math.PI/size;
			double fiD = (rocket[POS]-0.2*rocket[DIR])*2*Math.PI/size;
			Point2D start = getPoint(fi, dim.getX()/2+ 15, dim.getY()/2+ 15, dim);
			Point2D dest = getPoint(fiD, dim.getX()/2+ 17, dim.getY()/2+ 17, dim);
			Point2D bliza = getPoint(fi, dim.getX()/2+ 22, dim.getY()/2+ 22, dim);
			Point2D dalja = getPoint(fi, dim.getX()/2+ 8, dim.getY()/2+ 8, dim);
			
			Path2D raketla = new Path2D.Double();
			raketla.moveTo(bliza.getX(), bliza.getY());
			raketla.lineTo(dest.getX(), dest.getY());
			raketla.lineTo(dalja.getX(), dalja.getY());
			raketla.closePath();
			g2.draw(raketla);
		}

		
		g2.setColor(Color.BLACK);
		g2.setStroke(new BasicStroke(2f));
		g2.setFont(new Font("Arial", Font.BOLD, 20));
		g2.drawString("TURN: "+counter, (int)dim.getX()/2-50, (int)dim.getY()/2+10);
		g2.setFont(new Font("Arial", Font.PLAIN, 12));
		g2.setStroke(new BasicStroke(1f));
	}
	
	public static Point2D getPoint(double fi, double rx, double ry, Point2D dim) {
		return new Point2D.Double(dim.getX()/2+rx*Math.cos(fi),dim.getY()/2+ry*Math.sin(fi));
	}
	
	
}



